﻿//-----------------------------------------------------------------------
// <copyright file="SP0008 - DoNotDisposeSPSiteRootWebWhenUsingLockIssueOwnerOrSecondaryContact.cs" company="Adrien Siffermann">
//      Copyright © Adrien Siffermann 2008-2009. All rights reserved.
// </copyright>
// <summary>This file contains implementation of the DoNotDisposeSPSiteRootWebWhenUsingLockIssueOwnerOrSecondaryContact class.</summary>
// <website>http://sovfxcoprules.codeplex.com/</website>
//-----------------------------------------------------------------------

namespace SharePoint.FxCop.BestPracticesRules
{
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Defines a custom Code Analysis rule to check if the
    /// Dispose pattern is respected in the SharePoint Object Model
    /// when accessing to SPSite.LockIssue, SPSite.Owner or SPSite.SecondaryContact.
    /// </summary>
    public class DoNotDisposeSPSiteRootWebWhenUsingLockIssueOwnerOrSecondaryContact : BaseStaticAnalysisRule
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="DoNotDisposeSPSiteRootWebWhenUsingLockIssueOwnerOrSecondaryContact"/> class.
        /// </summary>
        public DoNotDisposeSPSiteRootWebWhenUsingLockIssueOwnerOrSecondaryContact()
            : base("DoNotDisposeSPSiteRootWebWhenUsingLockIssueOwnerOrSecondaryContact")
        {
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Checks the specified member.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>The collection of problems.</returns>
        public override ProblemCollection Check(Member member)
        {
            if (!BaseStaticAnalysisRule.CheckMember(member))
            {
                return null;
            }

            Method method = member as Method;
            this.InitWorkingVars();
            this.VarsToWatch.Clear();

            foreach (Instruction instruction in method.Instructions)
            {
                switch (instruction.OpCode)
                {
                    case OpCode.Newobj:
                        break;
                    case OpCode.Stloc_0:
                    case OpCode.Stloc_1:
                    case OpCode.Stloc_2:
                    case OpCode.Stloc_3:
                    case OpCode.Stloc_S:
                        break;
                    case OpCode.Ldloc_0:
                    case OpCode.Ldloc_1:
                    case OpCode.Ldloc_2:
                    case OpCode.Ldloc_3:
                    case OpCode.Ldloc_S:
                        this.CheckLdLoc(instruction);
                        break;
                    case OpCode.Call:
                    case OpCode.Callvirt:
                    case OpCode.Jmp:
                        this.CheckCall(instruction, method.FullName);
                        break;
                    case OpCode.Castclass:
                        break;
                    case OpCode.Ldstr:
                        break;
                    default:
                        this.InitWorkingVars();
                        break;
                }
            }

            return Problems;
        }
        #endregion

        #region Private Methods
        private void CheckLdLoc(Instruction instruction)
        {
            Local varLoaded = instruction.Value as Local;
            WatchedVar watchedVarLoaded = this.VarsToWatch.FirstOrDefault(v => v.Name == varLoaded.Name.Name);
            if (watchedVarLoaded != null)
            {
                this.IsInvoking = true;
                this.CurrentVar = watchedVarLoaded;
            }
            else if (varLoaded.Type.FullName == "Microsoft.SharePoint.SPSite")
            {
                this.VarsToWatch.Add(new WatchedVar() { Name = varLoaded.Name.Name, TypeFullName = varLoaded.Type.FullName, IgnoreProblems = true });
                this.CheckLdLoc(instruction);
            }
        }

        private void CheckCall(Instruction instruction, string methodName)
        {
            Method callee = instruction.Value as Method;
            string calleeName = callee.FullName;

            int j = calleeName.LastIndexOf('.');
            if (j > 0)
            {
                calleeName = calleeName.Substring(j + 1);
            }

            if (this.IsInvoking == true)
            {
                if (calleeName == "Dispose" && !this.CurrentVar.IgnoreProblems)
                {
                    this.Problems.Add(new Problem(GetResolution(methodName, this.CurrentVar.Name), instruction));
                }

                this.CheckSpecialProperties(calleeName, instruction);
                this.IsInvoking = false;
            }
            else if (this.IsAccessingProperty)
            {
                WatchedVar watchedProperty = this.CurrentVar.WatchedProperties.FirstOrDefault(v => v.Name == this.LastProperty);
                if (calleeName == "Dispose")
                {
                    if (watchedProperty != null)
                    {
                        this.Problems.Add(new Problem(GetResolution(methodName, watchedProperty.Name), instruction));
                    }
                }
                else
                {
                    if (watchedProperty == null)
                    {
                        this.CurrentVar.WatchedProperties.Add(new WatchedVar() { Name = this.LastProperty, TargetedNode = this.LastPropertyNode });
                    }
                }

                this.IsAccessingProperty = false;
            }
        }

        private void CheckSpecialProperties(string calleeName, Node currentNode)
        {
            if (this.CurrentVar.TypeFullName == "Microsoft.SharePoint.SPSite")
            {
                if (calleeName == "get_LockIssue"
                    || calleeName == "get_Owner"
                    || calleeName == "get_SecondaryContact")
                {
                    WatchedVar watchedProperty = this.CurrentVar.WatchedProperties.FirstOrDefault(v => v.Name == "RootWeb");
                    if (watchedProperty == null)
                    {
                        this.CurrentVar.WatchedProperties.Add(new WatchedVar() { Name = "RootWeb", TypeFullName = "Microsoft.SharePoint.SPWeb", TargetedNode = currentNode, ResolutionName = "RootWebProp" });
                    }
                }
                else if (calleeName == "get_RootWeb")
                {
                    WatchedVar watchedProperty = this.CurrentVar.WatchedProperties.FirstOrDefault(v => v.Name == "RootWeb");
                    if (watchedProperty != null)
                    {
                        this.LastProperty = calleeName.Substring(4);
                        this.IsAccessingProperty = true;
                    }
                }
            }
        }
        #endregion
    }
}
